

import UIKit
import CoreImage

let dataSourceURL = URL(string:"http://www.raywenderlich.com/downloads/ClassicPhotosDictionary.plist")!

class ListViewController: UITableViewController {
  
    lazy var photos = [Photo]()
    var photoQueue = PhotoQueue()
    
  
  override func viewDidLoad() {
    super.viewDidLoad()
    self.title = "Classic Photos"
    
    loadData()
  }
    
    func loadData() {
        
        UIApplication.shared.isNetworkActivityIndicatorVisible = true
        
        let task = URLSession.shared.dataTask(with: dataSourceURL) { [weak self] (data, resp, error) in
                    
            guard let self = self else {
                return
            }
            
            if let error = error {
                print(error)
                DispatchQueue.main.async {
                    UIApplication.shared.isNetworkActivityIndicatorVisible = false
                }
            }
            
            if let data = data {
                do {
                    if let dict = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: String] {
                        
                        for (name, url) in dict {
                            self.photos.append(Photo(name: name, url: url))
                        }
                        
                        DispatchQueue.main.async {
                            UIApplication.shared.isNetworkActivityIndicatorVisible = false
                            self.tableView.reloadData()
                        }
                    }
                                        
                } catch {
                    print(error)
                }
                
            }
        }
        task.resume()
    }
  
  // MARK: - Table view data source
  override func tableView(_ tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
    return photos.count
  }
  
  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) 
    
    if cell.accessoryView == nil {
      let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
      cell.accessoryView = indicator
    }
    
    let indicatorView = cell.accessoryView as! UIActivityIndicatorView
    
    let photo = photos[indexPath.row]
    
    cell.textLabel?.text = photo.name
    cell.imageView?.image = photo.image
    
    switch photo.state {
    case .failed:
        cell.textLabel?.text = "failed"
        indicatorView.stopAnimating()
    case .filtered:
        indicatorView.stopAnimating()
    case .new, .downloaded:
        indicatorView.startAnimating()
        if !tableView.isDragging && !tableView.isDecelerating {
            startOperations(for: photo, at: indexPath)
        }
    }
    
    return cell
  }
    
    override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        photoQueue.suspendAllOperations()
    }
    
    override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        
        if !decelerate {
            loadImagesForOnscreenCells()
            photoQueue.resumeAllOperations()
        }
    }
    
    override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        
        loadImagesForOnscreenCells()
        photoQueue.resumeAllOperations()
    }
    
    
    func startOperations(for photo: Photo, at indexPath: IndexPath) {
        switch photo.state {
        case .new:
            startDownload(for: photo, at: indexPath)
        case .downloaded:
            startFiltered(for: photo, at: indexPath)
        default:
            print("do nothing")
        }
    }
    
    func startDownload(for photo: Photo, at indexPath: IndexPath) {
        
        guard photoQueue.downloadsInProgress[indexPath] == nil else {
            return
        }
        
        let download = DownloadOperation(photo)
        download.completionBlock = {
            
            if download.isCancelled {
                return
            }
            
            DispatchQueue.main.async {
                self.photoQueue.downloadsInProgress.removeValue(forKey: indexPath)
                self.tableView.reloadRows(at: [indexPath], with: .fade)
            }
        }
        
        photoQueue.downloadsInProgress[indexPath] = download
        photoQueue.downloadQueue.addOperation(download)
    }
    
    func startFiltered(for photo: Photo, at indexPath: IndexPath) {
        guard photoQueue.downloadsInProgress[indexPath] == nil else {
            return
        }
        
        let filter = FilterOperation(photo)
        filter.completionBlock = {
            
            if filter.isCancelled {
                return
            }
            
            DispatchQueue.main.async {
                self.photoQueue.filtersInProgress.removeValue(forKey: indexPath)
                self.tableView.reloadRows(at: [indexPath], with: .fade)
            }
        }
        
        photoQueue.filtersInProgress[indexPath] = filter
        photoQueue.filterQueue.addOperation(filter)
    }

    func loadImagesForOnscreenCells() {
        if let paths = tableView.indexPathsForVisibleRows {
            
            var allPendingOperations = Set(photoQueue.downloadsInProgress.keys)
            allPendingOperations.formUnion(photoQueue.filtersInProgress.keys)
            
            let visiblePaths = Set(paths)
            var toBeCancelled = allPendingOperations
            toBeCancelled.subtract(visiblePaths)
            
            var toBeStarted = visiblePaths
            toBeStarted.subtract(allPendingOperations)
            
            for indexPath in toBeCancelled {
                
                if let download = photoQueue.downloadsInProgress[indexPath] {
                    download.cancel()
                }
                photoQueue.downloadsInProgress.removeValue(forKey: indexPath)
                
                if let filter = photoQueue.filtersInProgress[indexPath] {
                    filter.cancel()
                }
                photoQueue.filtersInProgress.removeValue(forKey: indexPath)
            }
            
            for indexPath in toBeStarted {
                let photo = photos[indexPath.row]
                startOperations(for: photo, at: indexPath)
            }
        }
    }
}
